﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Web;

namespace API.Models.Repository
{
    public interface IGenericRepositoryAsync<T> where T : class
    {
        Task<IQueryable<T>> Find();
        Task<IQueryable<T>> Find(Expression<Func<T, bool>> predicate = null);
        Task<IQueryable<T>> Find(IEnumerable<string> includes = null, Expression<Func<T, bool>> predicate = null);
        Task Add(T entity);
        Task Delete(T entity);
        Task Edit(T entity);
        Task<int> Save();
        void SetSession(SEC_SESSIONS currentSession);
    }

    public class GenericRepositoryAsync<C, T> : IGenericRepositoryAsync<T>
        where T : class, ICommonDbClass
        where C : DbContext, new()
    {
        public SEC_SESSIONS session { get; set; }

        public virtual void SetSession(SEC_SESSIONS currentSession)
        {
            this.session = currentSession;
        }

        public C _entities = new C();
        public C Context
        {
            get { return _entities; }
            set { _entities = value; }
        }

        public virtual async Task<IQueryable<T>> Find()
        {
            return await Find(new string[0], null);
        }

        public virtual async Task<IQueryable<T>> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate = null)
        {
            return await Find(new string[0], predicate);
        }

        public virtual async Task<IQueryable<T>> Find(IEnumerable<string> includes = null, System.Linq.Expressions.Expression<Func<T, bool>> predicate = null)
        {
            Func<IQueryable<T>> func = () =>
            {
                IQueryable<T> query = _entities.Set<T>();

                foreach (var i in includes)
                    query = query.Include(i);

                if (predicate != null)
                    query = query.Where(predicate);

                return query;
            };

            return await Task.Run(func);
        }

        public virtual async Task Add(T entity)
        {
            Action action = () =>
            {
                entity.DATECREATE = DateTime.Now;
                entity.DATEUPDATE = DateTime.Now;

                if (this.session != null)
                {
                    entity.USERCREATE_GUID = this.session.USER_GUID;
                    entity.USERUPDATE_GUID = this.session.USER_GUID;
                }

                _entities.Set<T>().Add(entity);
            };

            await Task.Run(action);
        }

        public virtual async Task Delete(T entity)
        {
            Action action = () =>
            {
                var record = _entities.Set<T>().SingleOrDefault(r => r.GUID_RECORD == entity.GUID_RECORD);
                if (record == null)
                    throw new Exception("Запись не найдена в базе");

                _entities.Set<T>().Remove(record);
            };

            await Task.Run(action);
        }

        public virtual async Task Edit(T entity)
        {
            Action action = () =>
            {
                entity.DATECREATE = DateTime.Now;
                entity.DATEUPDATE = DateTime.Now;

                if (this.session != null)
                {
                    entity.USERCREATE_GUID = this.session.USER_GUID;
                    entity.USERUPDATE_GUID = this.session.USER_GUID;
                }

                _entities.Entry(entity).State = System.Data.EntityState.Modified;
            };

            await Task.Run(action);
        }

        public virtual async Task<int> Save()
        {
            Func<int> func = () =>
            {
                return _entities.SaveChanges();
            };

            return await Task.Run(func);
        }
    }

    public class GenericRepositoryDirectoryAsync<C, T> : IGenericRepositoryAsync<T>
        where T : class, ICommonDirectoryDbClass
        where C : DbContext, new()
    {
        public SEC_SESSIONS session { get; set; }

        public virtual void SetSession(SEC_SESSIONS currentSession)
        {
            this.session = currentSession;
        }

        public C _entities = new C();
        public C Context
        {
            get { return _entities; }
            set { _entities = value; }
        }

        public virtual async Task<IQueryable<T>> Find()
        {
            return await Find(new string[0], null);
        }

        public virtual async Task<IQueryable<T>> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate = null)
        {
            return await Find(new string[0], predicate);
        }

        public virtual async Task<IQueryable<T>> Find(IEnumerable<string> includes = null, System.Linq.Expressions.Expression<Func<T, bool>> predicate = null)
        {
            Func<IQueryable<T>> func = () =>
            {
                IQueryable<T> query = _entities.Set<T>();

                foreach (var i in includes)
                    query = query.Include(i);

                if (predicate != null)
                    query = query.Where(predicate);

                if (session != null)
                    query = query
                        .Where(r => r.COMPANY_GUID == this.session.COMPANY_GUID);

                if (session == null)
                    throw new UnauthorizedAccessException("Запрос не авторизован. Нет доступа к данным");

                return query;
            };

            return await Task.Run(func);
        }

        public virtual async Task Add(T entity)
        {
            Action action = () =>
            {
                entity.DATECREATE = DateTime.Now;
                entity.DATEUPDATE = DateTime.Now;

                if (this.session != null)
                {
                    entity.COMPANY_GUID = this.session.COMPANY_GUID; //.GetValueOrDefault();

                    entity.USERCREATE_GUID = this.session.USER_GUID;
                    entity.USERUPDATE_GUID = this.session.USER_GUID;
                }

                _entities.Set<T>().Add(entity);
            };

            await Task.Run(action);
        }

        public virtual async Task Edit(T entity)
        {
            Action action = () =>
            {
                entity.DATECREATE = DateTime.Now;
                entity.DATEUPDATE = DateTime.Now;

                if (this.session != null)
                {
                    entity.COMPANY_GUID = this.session.COMPANY_GUID; //.GetValueOrDefault();
                    entity.USERCREATE_GUID = this.session.USER_GUID;
                    entity.USERUPDATE_GUID = this.session.USER_GUID;
                }

                _entities.Entry(entity).State = System.Data.EntityState.Modified;
            };

            await Task.Run(action);
        }

        public virtual async Task Delete(T entity)
        {
            Action action = () =>
            {
                var record = _entities.Set<T>().SingleOrDefault(r => r.GUID_RECORD == entity.GUID_RECORD);
                
                if (record == null)
                    throw new Exception("Запись не найдена в базе");

                _entities.Set<T>().Remove(record);
            };

            await Task.Run(action);
        }

        public virtual async Task<int> Save()
        {
            Func<int> func = () =>
            {
                return _entities.SaveChanges();
            };

            return await Task.Run(func);
        }
    }
}







/*
public virtual async void InsertOrUpdate<R>(R record) where R : class, ICommonDbClass
{
    if (record == null)
        return;

    if (record.GUID_RECORD == Guid.Empty)
        record.GUID_RECORD = Guid.NewGuid();

    record.DATECREATE = DateTime.Now;
    record.DATEUPDATE = DateTime.Now;

    if (this.session != null)
    {
        record.USERCREATE_GUID = this.session.USER_GUID;
        record.USERUPDATE_GUID = this.session.USER_GUID;
    }

    if (_entities.Set<R>().Any(r => r.GUID_RECORD == record.GUID_RECORD))
    {
        try
        {
            if (_entities.Entry(record).State == System.Data.EntityState.Modified)
            {
                _entities.Entry(record).CurrentValues.SetValues(record);
            }

            _entities.Entry(record).State = System.Data.EntityState.Modified;
        }
        catch
        {
            /// В случае повторного обновления записи
            /// возникает ошибка
            /// нужно как-то обработать. Но как?
        }
    }
    else
    {
        _entities.Set<R>().Add(record as R);
    }
}

public virtual async void InsertOrUpdate<R>(IEnumerable<R> records) where R : class, ICommonDbClass
{
    if (records == null)
        return;

    records.ToList().ForEach(r => this.InsertOrUpdate<R>(r));
}
*/